use chrono::NaiveDateTime;
use crate::schema::users::{self, dsl::*};
use crate::models::CRUD;

use diesel::{RunQueryDsl, prelude::*, result::Error, Insertable, Queryable};
use crate::models::user_info::UserInfo;


#[derive(Queryable, Debug, Serialize)]
pub struct User {
    pub id: i32,
    pub sid: i32,
    pub username: String,
    pub phone: String,
    pub password: String,
    pub role: Option<String>,
    pub created_at: Option<NaiveDateTime>,
    pub updated_at: Option<NaiveDateTime>,
}

impl User {
    pub fn find_by_phone(conn: &PgConnection, phone_str: String) -> Result<Self, Error> {
        users::table.filter(users::phone.eq(phone_str)).first::<User>(conn)
    }
}


// 用于返回用户信息的结构体
#[derive(Debug, Serialize, Deserialize)]
pub struct ResUserInfo {
    pub id: i32,
    pub username: String,
    pub phone: String,
    pub role: Option<String>,
    pub sex: Option<String>,
    pub address: Option<String>,
    pub school: Option<String>,
    pub remark: Option<String>,
    pub created_at: i64,
}

impl ResUserInfo {
    pub fn new(u: &User, user_info: &UserInfo) -> Self {
        ResUserInfo {
            id: u.id,
            username: u.username.clone(),
            phone: u.phone.clone(),
            role: u.role.clone(),
            sex: user_info.sex.clone(),
            address: user_info.address.clone(),
            school: user_info.school.clone(),
            remark: user_info.remark.clone(),
            created_at: u.created_at.unwrap().timestamp(),
        }
    }
}

impl ResUserInfo {
    pub fn get_by_uid(conn: &PgConnection, uid: i32) -> Option<Self> {
        if let Ok(u) = User::get_by_pk(&conn, uid) {
            let user_info = UserInfo::get_by_uid(&conn, u.id).unwrap_or(UserInfo {
                id: 0,
                uid: 0,
                sex: None,
                address: None,
                school: None,
                remark: None,
            });

            let res_user_info = ResUserInfo::new(&u, &user_info);

            return Some(res_user_info);
        }
        None
    }
}

#[derive(Debug, Insertable, AsChangeset, Serialize, Deserialize)]
#[table_name = "users"]
pub struct NewUser {
    pub sid: i32,
    pub username: String,
    pub phone: String,
    pub password: String,
    pub role: Option<String>,
}


impl CRUD<NewUser, NewUser, i32> for User {
    fn create(conn: &PgConnection, from: &NewUser) -> Result<Self, Error> {
        diesel::insert_into(users::table)
            .values(from)
            .get_result(conn)
    }

    fn read(conn: &PgConnection, page: i64, page_size: i64) -> Vec<Self> where
        Self: Sized {
        // let count = users::table.count().get_result::<i64>(conn);
        // vec![]

        users::table
            .order(users::id.asc())
            .limit(page_size)
            .offset((page - 1) * page_size)
            .load::<User>(conn.clone()).unwrap_or(vec![])
    }

    fn update(conn: &PgConnection, pk: i32, value: &NewUser) -> Result<Self, Error> where
        Self: Sized {
        diesel::update(users::table.find(pk))
            .set(value)
            .get_result(conn)
    }

    fn delete(conn: &PgConnection, pk: i32) -> Result<usize, Error> where
        Self: Sized {
        diesel::delete(users::table.filter(users::id.eq(pk))).execute(conn)
    }

    fn get_by_pk(conn: &PgConnection, pk: i32) -> Result<Self, Error> where
        Self: Sized {
        users::table.find(pk).first::<User>(conn)
    }
}


pub mod form {
    use serde::{Deserialize, Serialize};
    use crate::models::user::NewUser;
    use md5;

    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct NewUserForm {
        pub username: String,
        pub phone: String,
        pub password: String,
        pub role: Option<String>,
        pub sex: Option<String>,
        pub address: Option<String>,
        pub school: Option<String>,
        pub remark: Option<String>,
    }

    impl From<NewUserForm> for NewUser {
        fn from(form: NewUserForm) -> Self {
            Self {
                sid: 0,
                username: form.username,
                phone: form.phone,
                password: format!("{:x}", md5::compute(form.password)),
                role: form.role,
            }
        }
    }

    #[derive(Debug, Serialize, Deserialize)]
    pub struct NewTokenForm {
        pub phone: String,
        pub password: String,
    }
}
